/*
 * Copyright 2012 Finbudget.com
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package finbudget.client.ui.widget.upload;

import java.util.ArrayList;
import java.util.List;

import com.google.gwt.core.client.GWT;
import com.google.gwt.event.dom.client.ChangeEvent;
import com.google.gwt.event.dom.client.ChangeHandler;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.web.bindery.event.shared.EventBus;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.http.client.UrlBuilder;
import com.google.gwt.uibinder.client.UiBinder;
import com.google.gwt.uibinder.client.UiField;
import com.google.gwt.uibinder.client.UiHandler;
import com.google.gwt.user.client.Random;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.Anchor;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.FileUpload;
import com.google.gwt.user.client.ui.FormPanel;
import com.google.gwt.user.client.ui.FormPanel.SubmitCompleteEvent;
import com.google.gwt.user.client.ui.FormPanel.SubmitCompleteHandler;
import com.google.gwt.user.client.ui.Image;
import com.google.gwt.user.client.ui.InlineHTML;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.SimplePanel;
import com.google.gwt.user.client.ui.Widget;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
import com.gwtplatform.dispatch.shared.DispatchAsync;

import finbudget.client.event.ShowErrorMessageEvent;
import finbudget.client.ui.progress.ProgressBar;
import finbudget.shared.Constants;

public class UploadLine extends Composite {
	interface Binder extends UiBinder<Widget, UploadLine> {
	}
	private static final Binder binder = GWT.create(Binder.class);
	public interface UploadLineFactory {
		public UploadLine create(String keyNode, boolean readonly, Attachment attachment, UploadLineContainer container);
	}
	public interface UploadLineContainer {
		public void createNewLine();
		/**
		 * This method is called when upload process is finished.
		 * @param uploadLine
		 * @param success true in case uploading was successfull or false in case of failure
		 */
		public void onUploadComplete(UploadLine uploadLine, boolean success);
	}
	private enum State {
		HIDDEN,	// the control is readonly and no attachment set
		FILESELECT, 	// the control is not readonly and no attachment set. Show file upload control
		SHOWLINK,		// the control is readonly and attachment set
		EDITLINK,		// the control is not readonly and attachment set
		UPLOADING,		// uploading file
		DELETING,
		DELETED,
		ERROR_DELETING,
		UPLOAD_ERROR
	}
	private State state;

	@UiField FileUpload fileUpload;
	@UiField FormPanel form;
	@UiField Anchor aLink;
	@UiField SimplePanel panelProgress;
	@UiField Image imgDelete;
	@UiField Label labelFileName;
	@UiField Label labelSize;
	@UiField Label labelDeleting;
	@UiField Label labelDeleted;
	@UiField Label labelErrorDeleting;
	@UiField Label labelErrorUploading;
	@UiField InlineHTML labelError;
	@UiField Button buttonOK;

	private final DispatchAsync dispatcher;
//	private final String keyNode;
	private boolean readonly;
	private final Attachment attachment;
	private Timer uploadStatus;
	private final ProgressBar progressBar;
	/**
	 * The random number is used to identify attachment for a given node
	 */
	private final int random;
	private final List<HandlerRegistration> handlers;
	private final UploadLineContainer container;
	private final EventBus eventBus;

	@Inject
	protected UploadLine(DispatchAsync dispatch, EventBus eventBus, @Assisted String keyNode,
				@Assisted boolean isReadonly, @Assisted Attachment attach, @Assisted UploadLineContainer cont) {
		initWidget(binder.createAndBindUi(this));

		this.dispatcher = dispatch;
//		this.keyNode = keyNode;
		this.readonly = isReadonly;
		this.attachment = attach == null ? new Attachment() : attach;
		this.random = Random.nextInt();
		this.handlers = new ArrayList<HandlerRegistration>();
		this.progressBar = new ProgressBar();
		this.container = cont;
		this.eventBus = eventBus;
		imgDelete.setTitle(imgDelete.getAltText());

		panelProgress.add(progressBar);
		if (readonly) {
			state = this.attachment.getFileName() == null ? State.HIDDEN : State.SHOWLINK;
		} else {
			state = this.attachment.getFileName() == null ? State.FILESELECT : State.EDITLINK;
		}
		labelFileName.setText(attachment.getFileName());
		showState();

		if (state == State.FILESELECT || state == State.HIDDEN) {
			form.setMethod(FormPanel.METHOD_POST);
			form.setEncoding(FormPanel.ENCODING_MULTIPART);
			form.setAction(Constants.urlFileUpload + "?" + Constants.paramKeyNode + "=" + keyNode + "&" + Constants.paramRandom + "=" + random);
			handlers.add(fileUpload.addChangeHandler(new ChangeHandler() {
				@Override
				public void onChange(ChangeEvent event) {
					state = State.UPLOADING;
					showState();
					if (uploadStatus != null) {
						uploadStatus.cancel();
					}
					uploadStatus = new Timer() {
						@Override
						public void run() {
							checkUploadStatus();
						}
					};
					uploadStatus.scheduleRepeating(1000);	// Each second
					fileUpload.setName("file");
					container.createNewLine();
					labelFileName.setText(fileUpload.getFilename());
					form.submit();
				}
			}));
			final UploadLine thisUploadLine = this;
			handlers.add(form.addSubmitCompleteHandler(new SubmitCompleteHandler() {
				@Override
				public void onSubmitComplete(SubmitCompleteEvent event) {
					if (uploadStatus != null) {
						uploadStatus.cancel();
						uploadStatus = null;
					}
					String result = event.getResults();
					String blobKey = null;
					if (result != null) {
						int start = result.indexOf(Constants.blobStart);
						if (start >= 0) {
							// Blob key start sequence found
							String blobCandidate = result.substring(start + Constants.blobStart.length());
							int end = blobCandidate.indexOf(":");
							if (end >= 0) {
								// end symbol found
								blobKey = blobCandidate.substring(0, end);
								attachment.setBlobKey(blobKey);
								attachment.setFileName(fileUpload.getFilename());
							}
						}
					}
					if (blobKey != null) {
						state = readonly ? State.SHOWLINK : State.EDITLINK;
					} else {
						state = State.UPLOAD_ERROR;
						labelError.setHTML(result);
					}
					showState();
					container.onUploadComplete(thisUploadLine, state != State.UPLOAD_ERROR);
				}
			}));
		}
	}

	public void cleanup() {
		for (HandlerRegistration handler : handlers) {
			handler.removeHandler();
		}
		handlers.clear();
	}

	private void showState() {
		form.setVisible(state == State.FILESELECT);
		imgDelete.setVisible(state == State.EDITLINK);
		aLink.setVisible(state == State.EDITLINK || state == State.SHOWLINK);
		labelFileName.setVisible(state == State.DELETING || state == State.DELETED || state == State.ERROR_DELETING || state == State.UPLOADING || state == State.UPLOAD_ERROR);
		labelSize.setVisible(state != State.FILESELECT);
		labelDeleting.setVisible(state == State.DELETING);
		labelDeleted.setVisible(state == State.DELETED);
		labelErrorDeleting.setVisible(state == State.ERROR_DELETING);
		labelErrorUploading.setVisible(state == State.UPLOAD_ERROR);
		labelError.setVisible(state == State.ERROR_DELETING || state == State.UPLOAD_ERROR);
		buttonOK.setVisible(state == State.ERROR_DELETING);
		panelProgress.setVisible(state == State.UPLOADING);

		if (state == State.EDITLINK || state == State.SHOWLINK) {
			UrlBuilder builder = Window.Location.createUrlBuilder();
			builder = builder.setPath(Constants.urlDownload);
			builder = builder.setHash(null);
			builder = builder.setParameter(Constants.paramBlob, attachment.getBlobKey());
			aLink.setHref(builder.buildString());
			// 2014-02-13 vlak: The _blank is needed not to break current requests that might be sent to the server (like waiting for lists in
			// AFE forms. The risk is that browser could ask for authorization.
			aLink.setTarget("_blank");
			aLink.setText(attachment.getFileName());
		}
	}

	private void checkUploadStatus() {
		if (uploadStatus != null) {
			uploadStatus.cancel();
			uploadStatus = null;
		}
//		dispatcher.execute(new GetUploadStatusAction(keyNode, random), new AsyncCallback<GetUploadStatusResult>() {
//			int counter = 0;
//			int lastProgressValue = -1;
//
//			@Override
//			public void onFailure(Throwable caught) {
//				checkCounter();
//			}
//
//			@Override
//			public void onSuccess(GetUploadStatusResult result) {
//				if (result != null && result.getPercent() >= 0 && result.getPercent() <= 100) {
//					int value = result.getPercent();
//					if (lastProgressValue == value) {
//						checkCounter();
//					} else {
//						lastProgressValue = value;
//						counter = 0;
//						if (value >= 0 && value <= 100) {
//							progressBar.setProgress(value);
//						}
//					}
//				} else {
//					checkCounter();
//				}
//			}
//
//			private void checkCounter() {
//				if (counter > 60) {
//					if (uploadStatus != null) {
//						uploadStatus.cancel();
//						uploadStatus = null;
//					}
//				}
//				counter++;
//			}
//		});
	}

	public String getStringValue() {
		return attachment.getStringValue();
	}

	@UiHandler("imgDelete")
	protected void onFileDelete(ClickEvent event) {
		if (state != State.EDITLINK) {
			// This should not be happened.
			eventBus.fireEvent(new ShowErrorMessageEvent("Невозможно удалить файл: форма не находится в режиме редактирования", null));
		} else {
			// Delete file
			state = State.DELETING;
			showState();
			dispatcher.execute(new DeleteBlobAction(attachment.getBlobKey()), new AsyncCallback<DeleteBlobResult>() {
				@Override
				public void onFailure(Throwable caught) {
					state = State.ERROR_DELETING;
					labelError.setHTML(caught == null ? "" : caught.getMessage());
					showState();
				}

				@Override
				public void onSuccess(DeleteBlobResult result) {
					state = State.DELETED;
					attachment.setBlobKey(null);
					attachment.setFileName(null);
					showState();
				}
			});
		}
	}

	@UiHandler("buttonOK")
	protected void buttonOkayClicked(ClickEvent event) {
		if (state == State.ERROR_DELETING) {
			state = readonly ? State.SHOWLINK : State.EDITLINK;
		}
		showState();
	}

	public void setEnabled(boolean enabled) {
		readonly = !enabled;
		switch (state) {
		case HIDDEN:
		case FILESELECT:
			state = enabled ? State.FILESELECT : State.HIDDEN;
			break;
		case SHOWLINK:
		case EDITLINK:
			state = enabled ? State.EDITLINK : State.SHOWLINK;
			break;
		case DELETED:
		case DELETING:
		case ERROR_DELETING:
		case UPLOADING:
		case UPLOAD_ERROR:
		default:
			break;
		}
		showState();
	}

	public String getBlobKey() {
		return (attachment == null) ? null : attachment.getBlobKey();
	}

	public String getFileName() {
		return (attachment == null) ? null : attachment.getFileName();
	}

}
